﻿/*
   Copyright 2012 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.ServiceModel;
using Flower.Services.Data;

namespace Flower.Services
{
    /// <summary>
    /// The directory interface.
    /// 
    /// For all reading methods, if it is said that the method returns entries with their payload,
    /// the type of the returned value (DirectoryEntry derived class) depends on the type of the entry;
    /// otherwise, DirectoryEntryInfo is returned regardless of the type of an entry.
    /// 
    /// In all operations it is not distinguishable whether an entry doesn't exist or the calling identity
    /// has no permission to read it.
    /// 
    /// All path/names comparisons are case insensitive.
    /// </summary>
    [ServiceContract(Namespace = XmlNamespace.Directory)]
    public interface IDirectory
    {
        /// <summary>
        /// Returns the path of an entry with a specified id.
        /// 
        /// Throws EntryNotFound fault if the entry with the specified id doesn't exist.
        /// </summary>
        /// <param name="id">The id of an existing entry.</param>
        /// <returns>The path of the entry with the specified id.</returns>
        [OperationContract]
        [FaultContract(typeof(DirectoryFault))]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        string GetPathById(string id);

        /// <summary>
        /// Returns an entry with its payload by its path or id.
        /// </summary>
        /// <param name="what">The path or id of the entry.</param>
        /// <returns>
        /// The found entry or null if the entry doesn't exist.
        /// </returns>
        [OperationContract]
        [FaultContract(typeof(DirectoryFault))]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        DirectoryEntry Get(string what);

        /// <summary>
        /// Returns an entry without its payload by its path or id.
        /// </summary>
        /// <param name="what">The path or id of the entry.</param>
        /// <returns>The found entry or null if the entry doesn't exist.</returns>
        [OperationContract]
        [FaultContract(typeof(DirectoryFault))]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        DirectoryEntryInfo GetInfo(string what);

        /// <summary>
        /// Returns child entries of an entry. The entries are returned with their payload.
        /// 
        /// If the parent entry doesn't exist, an empty collection is returned.
        /// </summary>
        /// <param name="parent">The path or id of the parent.</param>
        /// <param name="entryTypes">The flags specifying entries of which type to return.</param>
        /// <param name="property">The property by which the entries are to be sorted.</param>
        /// <param name="order">The sorting order.</param>
        /// <param name="offset">The zero based index of the first returned entry among found.</param>
        /// <param name="limit">
        /// The upper limit on the indexes to return, so that max. entries returned = limit - offset. 
        /// The limit less or equal to the offset means no limit.
        /// </param>
        /// <returns>
        /// The collection of the child entries sorted and filtered according to the input arguments.
        /// </returns>
        [OperationContract]
        [FaultContract(typeof(DirectoryFault))]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        IList<DirectoryEntry> GetChildren(string parent, DirectoryEntryTypes entryTypes, SortProperty property, SortOrder order, long offset, long limit);

        /// <summary>
        /// Returns child entries of an entry. The entries are returned without their payload.
        /// 
        /// If the parent entry doesn't exist, an empty collection is returned.
        /// </summary>
        /// <param name="parent">The path or id of the parent.</param>
        /// <param name="entryTypes">The flags specifying entries of which type to return.</param>
        /// <param name="property">The property by which the entries are to be sorted.</param>
        /// <param name="order">The sorting order.</param>
        /// <param name="offset">The zero based index of the first returned entry among found.</param>
        /// <param name="limit">
        /// The upper limit on the indexes to return, so that max. entries returned = limit - offset. 
        /// The limit less or equal to the offset means no limit.
        /// </param>
        /// <returns>
        /// The collection of the child entries sorted and filtered according to the input arguments.
        /// </returns>
        [OperationContract]
        [FaultContract(typeof(DirectoryFault))]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        IList<DirectoryEntryInfo> GetChildrenInfo(string parent, DirectoryEntryTypes entryTypes, SortProperty property, SortOrder order, long offset, long limit);

        /// <summary>
        /// Counts the number of children of an entry.
        /// 
        /// If the specified parent entry doesn't exist, returns zero.
        /// </summary>
        /// <param name="parent">The path or id of the parent.</param>
        /// <param name="entryTypes">The flags specifying entries of which type to count.</param>
        /// <returns>The number of children of the specified parent.</returns>
        [OperationContract]
        [FaultContract(typeof(DirectoryFault))]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        long CountChildren(string parent, DirectoryEntryTypes entryTypes);

        /// <summary>
        /// Finds the n-th nearest ancestor of an entry and returns it with payload.
        /// 
        /// If the child is specified as a path, the deepest existing entry on the path is searched and
        /// the operation is performed against it. For example, suppose one requests 0-th ancestor of type Set
        /// by the path '/Sets/Shared/Set/Subset' where 'Set' is a set and it exists, 'Subset' is a subset
        /// and it doesn't exist. Then, the set '/Sets/Shared/Set' is returned.
        /// 
        /// If the child is specified by id and it doesn't exists, the EntryNotFound fault is thrown.
        /// </summary>
        /// <param name="child">The id of an entry or path starting from which to search for the ancestor.</param>
        /// <param name="n">
        /// How many found ancestors to skip assuming that the search starts from the child entry itself.
        /// 
        /// For example, if n = 0, and the child entry satisfies ancestorEntryTypes filter, than the child
        /// entry is returned itself. If, in the same case, n = 1, the nearest ancestor satisfiyng 
        /// ancestorEntryTypes is returned.
        /// </param>
        /// <param name="ancestorEntryTypes">The bitflags specifying entries of which types are to be returned.</param>
        /// <returns>The found ancestor or null if no ancestor satisfying the criteria is found.</returns>
        [OperationContract]
        [FaultContract(typeof(DirectoryFault))]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        DirectoryEntry GetAncestor(string child, int n, DirectoryEntryTypes ancestorEntryTypes);

        /// <summary>
        /// Finds the n-th nearest ancestor of an entry and returns it without payload.
        /// 
        /// If the child is specified as a path, the deepest existing entry on the path is searched and
        /// the operation is performed against it. For example, suppose one requests 0-th ancestor of type Set
        /// by the path '/Sets/Shared/Set/Subset' where 'Set' is a set and it exists, 'Subset' is a subset
        /// and it doesn't exist. Then the set '/Sets/Shared/Set' is returned.
        /// 
        /// If the child is specified by id and it doesn't exists, the EntryNotFound fault is thrown.
        /// </summary>
        /// <param name="child">The id of an entry or path starting from which to search for the ancestor.</param>
        /// <param name="n">
        /// How many found ancestors to skip assuming that the search starts from the child entry itself.
        /// 
        /// For example, if n = 0, and the child entry satisfies ancestorEntryTypes filter, than the child
        /// entry is returned itself. If, in the same case, n = 1, the nearest ancestor satisfiyng 
        /// ancestorEntryTypes is returned.
        /// </param>
        /// <param name="ancestorEntryTypes">The flags specifying entries of which types are to be returned.</param>
        /// <returns>The found ancestor or null if no ancestor satisfying the criteria is found.</returns>
        [OperationContract]
        [FaultContract(typeof(DirectoryFault))]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        DirectoryEntryInfo GetAncestorInfo(string child, int n, DirectoryEntryTypes ancestorEntryTypes);

        /// <summary>
        /// Creates or updates a directory entry. 
        /// 
        /// If the parent is specified as a path, the entry with that path may not exist.
        /// In this case, the deepest existing entry on the path is searched and the rest of
        /// the path is created as folders. The folders in this case each inherit ACL from its
        /// parent: all permissions are inherited as-is, but if a parent has create-children permission
        /// for a role, the same role will be granted the write permission on the created folders.
        /// 
        /// If the parent is specified as id and an entry with the id doesn't exist,
        /// the EntryNotFound fault is thrown.
        /// 
        /// If the entry is being updated (the id of the entry is specified), the parent may be null.
        /// In this case the entry is saved under its current parent. If the entry is created, the
        /// parent must be specified.
        /// 
        /// If the entry is being updated (the id of the entry is specified) and the parent specified
        /// is not its current one, the entry is moved to the new parent.
        /// 
        /// If the parent entry already has a child with the same name as the entry being saved,
        /// the NameCollision fault is thrown.
        /// </summary>
        /// <param name="parent">The path or id of the parent entry.</param>
        /// <param name="entry">The entry to be saved.</param>
        /// <returns>The id of the saved entry.</returns>
        [OperationContract]
        [FaultContract(typeof(DirectoryFault))]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        string Save(string parent, DirectoryEntry entry);

        /// <summary>
        /// Moves an entry from the current parent to another.
        /// 
        /// If the destination is specified as a path, the entry with that path may not exist.
        /// In this case, the deepest existing entry on the path is searched and the rest of
        /// the path is created as folders. The folders in this case each inherit ACL from its
        /// parent: all permissions are inherited as-is, but if a parent has create-children permission
        /// for a role, the same role will be granted the write permission on the created folders.
        /// 
        /// If the entry to be moved doesn't exist, the EntryNotFound fault is thrown.
        /// 
        /// If the parent entry already has a child with the same name as the entry being saved,
        /// the NameCollision fault is thrown.
        /// </summary>
        /// <param name="what">The path or id of the entry to move.</param>
        /// <param name="where">The new parent of the entry.</param>
        [OperationContract]
        [FaultContract(typeof(DirectoryFault))]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        void Move(string what, string where);

        /// <summary>
        /// Removes an entry.
        /// 
        /// If one of the ancestors of the removed entry is a set and the parent is a folder,
        /// the parent folder is removed if it becomes empty. This is done recursively up to
        /// the set ancestor.
        /// 
        /// All descendants are removed with their ancesotor.
        /// 
        /// If the entry to be removed doesn't exist, nothing happens.
        /// </summary>
        /// <param name="what">The path or id of the entry to remove.</param>
        [OperationContract]
        [FaultContract(typeof(DirectoryFault))]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        void Remove(string what);

        /// <summary>
        /// Checks whether an entry newer than a specified timestamp exists.
        /// </summary>
        /// <param name="what">The path or id of the entry to check.</param>
        /// <param name="timestamp">The last update timestamp.</param>
        /// <returns>True if the specified entry exists and it is newer than the specified timestamp; otherwise, false.</returns>
        [OperationContract]
        [FaultContract(typeof(DirectoryFault))]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        bool Exists(string what, DateTime timestamp);

        /// <summary>
        /// Executes a script with specified parameters.
        /// </summary>
        /// <param name="script">The script to execute.</param>
        /// <param name="args">Arguments that will appear as attributes of $in object.</param>
        /// <returns>Attributes of $out object.</returns>
        [OperationContract]
        [FaultContract(typeof(DirectoryFault))]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        ICollection<ScriptParameter> ExecuteScript(string script, ICollection<ScriptParameter> args);
    }
}
